/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef SCENE_HH_
#define SCENE_HH_

#include "carina/common/global.hh"

#include "carina/camera.hh"
#include "carina/math.hh"
#include "carina/attribute.hh"

#include <queue>

namespace Carina
{
class Scene;
class ResourceManager;
typedef shared_ptr<ResourceManager> ResourceManagerPtr;
class LightingManager;
typedef shared_ptr<LightingManager> LightingManagerPtr;

class GraphicsEngineContext;
typedef shared_ptr<GraphicsEngineContext> GraphicsEngineContextPtr;

class LightSource;
typedef shared_ptr<LightSource> LightSourcePtr;

class GlobalLightSource;
typedef shared_ptr<GlobalLightSource> GlobalLightSourcePtr;
typedef shared_ptr<const GlobalLightSource> GlobalLightSourceConstPtr;

class LightSourceNode;
typedef shared_ptr<LightSourceNode> LightSourceNodePtr;

class Window;
typedef shared_ptr<Window> WindowPtr;

class Node
{
public:
    virtual ~Node() {}

    virtual void draw(Scene& scene)=0;

    virtual void notifyTransform(const Matrix4& wmat)=0;

    virtual ConfigurablePtr getConfigurable()=0;
    virtual ConfigurableConstPtr getConfigurable() const=0;
};

template<class T>
inline T& GetRef(T& t) { return t; }

template<class T>
inline T& GetRef(shared_ptr<T>& t) { return t; }

typedef shared_ptr<Node> NodePtr;
typedef shared_ptr<const Node> NodeConstPtr;

class Drawable: public Configurable
{
public:
    virtual ~Drawable() {}

    virtual void draw(Scene& scene, const Matrix4& mat)=0;

	CE_NO_ATTRIBUTES(CE_POLYMORPHIC, NullBaseClass)
};

typedef shared_ptr<Drawable> DrawablePtr;

class DrawableNode: public Node
{
    DrawablePtr m_Drawable;
    Matrix4     m_WorldMatrix;
public:
    DrawableNode(const DrawablePtr& drawable);
    virtual ~DrawableNode();

    virtual void draw(Scene& scene);

    virtual void notifyTransform(const Matrix4& wmat);

    virtual ConfigurablePtr getConfigurable();
    virtual ConfigurableConstPtr getConfigurable() const;
};

typedef shared_ptr<DrawableNode> DrawableNodePtr;

class DrawRequest
{
public:
    virtual ~DrawRequest() {}

    virtual void drawObject(Scene& scene)=0;
};

typedef shared_ptr<DrawRequest> DrawRequestPtr;

class UnlitDrawableNode: public DrawableNode, public DrawRequest, public enable_shared_from_this<UnlitDrawableNode>
{
public:
    UnlitDrawableNode(const DrawablePtr& drawable);
    virtual ~UnlitDrawableNode();

    virtual void draw(Scene& scene);

    virtual void drawObject(Scene& scene);
};

typedef shared_ptr<UnlitDrawableNode> UnlitDrawableNodePtr;

class TransformNode;
typedef shared_ptr<TransformNode> TransformNodePtr;
typedef shared_ptr<const TransformNode> TransformNodeConstPtr;

class TransformNode: public Node, public Configurable, public enable_shared_from_this<TransformNode>
{
    Matrix4                 m_WorldMatrix;
    DynamicArray<NodePtr>   m_Children;
public:
    TransformNode();
    TransformNode(const Matrix4& wmat, const Matrix4& lmat);
     ~TransformNode();

    virtual void draw(Scene& scene);

    size_t getChildrenCount() const;
    NodePtr getChild(size_t idx);
    NodeConstPtr getChild(size_t idx) const;

    void addChild(const NodePtr& node);
    void removeChildren(size_t lhs, size_t rhs);
    void removeChild(size_t idx);
    void clear();

    Matrix4 getWorldMatrix() const;

    void setLocalMatrix(const Matrix4& mat);
    void multMatrix(const Matrix4& mat);

    virtual void notifyTransform(const Matrix4& wmat);

    virtual ConfigurablePtr getConfigurable();
    virtual ConfigurableConstPtr getConfigurable() const;
protected:
	CE_DECLARE_NAME_TYPE(LocalMatrixName, "Local matrix");
	typedef Attribute<Matrix4, LocalMatrixName, CE_RANGE_NONE> LocalMatrixBaseAttribute;

	class LocalMatrixAttribute: public LocalMatrixBaseAttribute
	{
		TransformNode&  m_Parent;
	public:
		LocalMatrixAttribute(TransformNode& parent, Matrix4& mat)
			:	LocalMatrixBaseAttribute(mat),
				m_Parent(parent) {}

		using LocalMatrixBaseAttribute::operator->;
		using LocalMatrixBaseAttribute::operator=;
		using LocalMatrixBaseAttribute::operator Matrix4&;
		using LocalMatrixBaseAttribute::operator const Matrix4&;

		virtual void setValue(const Matrix4* data, size_t) override { m_Parent.setLocalMatrix(*reinterpret_cast<const Matrix4*>(data)); }
	};

	Matrix4 m_LocalMatrix;

	CE_ATTRIBUTES(CE_POLYMORPHIC, NullBaseClass, 
			      CE_CREATE_CUSTOM_ATTRIBUTE(LocalMatrixAttribute, *this, m_LocalMatrix));
};

class ResizeWindowEvent;

class Scene
{
    size_t              m_MSAA;
    CameraPtr           m_Camera;

    RendererPtr         m_Renderer;

    TransformNodePtr    m_Root;

    enum
    {
        ALBEDO_TEXTURE,
        NORMAL_VECTOR_TEXTURE,
        GBUFFER_TEXTURES
    };

    FBAttachmentArray                   m_GBufferTextures;
    FBAttachmentInfo                    m_GBufferDepthTexture;
    FramebufferPtr                      m_GBuffer;

    BlendStateObjectPtr                 m_BlendState,
                                        m_AmbBlendState;
    StateObjectPtr                      m_RenderingState,
                                        m_LightingState;
    DepthStencilStateObjectPtr          m_DepthStencilState;

    GlobalLightSourcePtr                m_AmbientLight;

    DataVector<GlobalLightSourcePtr>    m_DLSources;
    DataVector<LightSourceNodePtr>      m_LightSources;

    std::queue<DrawRequestPtr>          m_Unlit;
public:
    Scene(const GraphicsEngineContextPtr& ctx);
    Scene(const LightingManagerPtr& lmgr, const ResourceManagerPtr& res_mgr, size_t w, size_t h, size_t samples = 1u);

    void draw();

    TransformNodePtr getRoot() { return m_Root; }
    TransformNodeConstPtr getRoot() const { return m_Root; }
    RendererPtr getRenderer() const { return m_Renderer; }

    CameraPtr getCamera() const { return m_Camera; }

    size_t getMSAA() const { return m_MSAA; }

    void setAmbientLight(const Vector3& amb);
    Vector3 getAmbientLight() const;

    void addGlobalLightSource(const GlobalLightSourcePtr& dl);
    GlobalLightSourcePtr getGlobalLightSource(size_t idx);
    GlobalLightSourceConstPtr getGlobalLightSource(size_t idx) const;
    size_t getGlobalLightSourceCount() const;
    void removeGlobalLightSource(size_t idx);
    void removeGlobalLightSources(size_t lhs, size_t rhs);
    void clearGlobalLightSources();

    void pushSpatialLightSource(const LightSourceNodePtr& ls);
    void pushUnlit(const DrawRequestPtr& d);

    void resize(size_t w, size_t h);
private:
    // TODO: inheriting constructors or just a more clever class splitting than the current one.
    void init(const LightingManagerPtr& lmgr, const ResourceManagerPtr& res_mgr, size_t w, size_t h, size_t samples);
};

typedef shared_ptr<Scene> ScenePtr;
}

#endif /* SCENE_HH_ */
